package com.passion.plugins;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Sets;
import com.passion.context.BizServiceBeanContext;
import com.passion.context.StdServiceContext;
import com.passion.domain.entity.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;

/**
 * 建类说明：完整sql打印拦截器
 *
 * @author 中国印
 * @since 2022年02月16日 21:34:31
 */
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
    @Signature(type = StatementHandler.class, method = "getBoundSql", args = {}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
@Slf4j
@Component
public class SqlFormatPlugin implements Interceptor {

    @Value("${custom-sql.show-format-sql:true}")
    private Boolean showFormatSql;
    @Value("${custom-sql.show-slow-sql:true}")
    private Boolean showSlowSql;
    @Value("${custom-sql.slow-sql-threshold:2000}")
    private Integer slowSqlThreshold;
    @Value("${custom-sql.ignore-sql-ids:}")
    private String ignoreSqlIds;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        final Object[] queryArgs = invocation.getArgs();
        if (queryArgs==null || queryArgs.length == 0 || !(queryArgs[0] instanceof MappedStatement)) {
            return invocation.proceed();
        }
        final MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
        final Object parameter = queryArgs[1];

        BoundSql boundSql = mappedStatement.getBoundSql(parameter);

        return this.printExecuteSql(invocation, mappedStatement, boundSql);
    }

    /**
     * 方法说明：打印执行的sql
     *
     * @param invocation 调用
     * @param ms         MappedStatement对象
     * @param boundSql   绑定sql
     * @throws InvocationTargetException 调用目标异常
     * @throws IllegalAccessException    非法访问异常
     */
    public Object printExecuteSql(Invocation invocation, MappedStatement ms, BoundSql boundSql) throws InvocationTargetException, IllegalAccessException {
        long start = System.currentTimeMillis();
        Object returnValue = invocation.proceed();
        long end = System.currentTimeMillis();
        long time = (end - start);
        Configuration configuration = ms.getConfiguration();
        // 忽略打印sql的id集合
        Set<String> ignoreSqlIdSet = Sets.newHashSet();
        if (StringUtils.isNotBlank(ignoreSqlIds)) {
            ignoreSqlIdSet.addAll(Arrays.asList(ignoreSqlIds.replace("，", ",").replace(" ", "").split(",")));
        }
        // sql对应的DAO层id标识，即方法名全限定名
        String sqlId = ms.getId();
        if (!ignoreSqlIdSet.contains(sqlId)) {
            // sql执行耗时，单位毫秒
            String timeStr = time + " ms;";
            // 完整的sql语句(填充了参数后的语句)
            String wholeSql = fillSqlParams(configuration, boundSql) + ";";
            StringBuffer sb = new StringBuffer();
            sb.append("\n||============================ Execute SQL Start ============================");
            sb.append("\n||     SqlId: ").append(sqlId);
            sb.append("\n||  CostTime: ").append(timeStr);
            sb.append("\n|| ActualSql: ").append(wholeSql);
            sb.append("\n||============================ Execute SQL End ==============================");
            // 如果日志文件sql日志开启，则sql记录到文件
            if (showFormatSql) {
                // 日志记录sql
                log.info(sb.toString());
            }
            // 如果慢sql记录开启，并且sql耗时超过阈值，则文件记录慢sql
            if (showSlowSql && time >= slowSqlThreshold) {
                // 日志记录sql
                log.error("Found slow SQL(threshold：{}ms): {}", slowSqlThreshold, sb);
            }
            // 异步记录SQL
            SysUser sysUser = BizServiceBeanContext.userContext.getSysUser();
            StdServiceContext.logSqlService.saveSqlLog(sysUser, sqlId, wholeSql, time);
        }
        return returnValue;
    }

    public static String fillSqlParams(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (CollectionUtils.isNotEmpty(parameterMappings)) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    }
                }
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        String value = "";
        if (obj == null) {
            return value;
        }
        if (obj instanceof String) {
            value = "'" + obj + "'";
        } else if (obj instanceof Date) {
            // 日期类型的转成字符串
            String format = DateUtil.format((Date) obj, DatePattern.NORM_DATETIME_PATTERN);
            if (StringUtils.isNotBlank(format)) {
                value = "'" + format + "'";
            }
        } else {
            value = obj.toString();
        }
        return value;
    }

}
